import Geo from "./Geo.js";
import Mat from "./Mat.js";
import { Matrix4, Vector3 } from "../three.module.js";

const defAttr = () => ({
  mat: null,
  geo: null,

  transformM4: new Matrix4(), // 全部变换矩阵
  tranM4NeedUpdate: false,

  depthTest: true, //深度测试
});

export default class Obj3D {
  constructor(attr) {
    Object.assign(this, defAttr(), attr);

    this.parent = null;
    this.children = [];
  }

  init(gl) {
    const { mat, geo } = this;
    geo && geo.init(gl);
    mat && mat.init(gl);
  }

  setUniform(name, uniform) {
    const { mat, children } = this;
    mat && mat.setData(name, uniform);

    for (let obj of children) {
      obj.setUniform(name, uniform);
    }
  }

  setMap(name, texture) {
    const { mat, children } = this;
    mat && mat.setMap(name, texture);

    for (let obj of children) {
      obj.setUniform(name, texture);
    }
  }

  setAttrib(name, attrib) {
    const { geo, children } = this;
    geo && geo.setData(name, attrib);

    for (let obj of children) {
      obj.setAttrib(name, attrib);
    }
  }

  update(gl, attribs, uniforms) {
    const { mat, geo, children } = this;
    mat && mat.update(gl, uniforms);
    geo && geo.update(gl, attribs);

    // for (let obj of children) {
    //     obj.mat.update(gl, uniforms);
    //     obj.geo.update(gl, attribs);
    // }
  }

  draw(gl) {
    const { depthTest, mat, geo } = this;
    const { mode } = mat;
    const { drawType, count } = geo;

    depthTest ? gl.enable(gl.DEPTH_TEST) : gl.disable(gl.DEPTH_TEST);
    if (!(mat == null || geo == null)) {
      for (let type of mode) {
        this[`_${drawType}`](gl, count, type);
      }
    }

    this.reset(gl);
  }

  reset(gl) {
    gl.bindTexture(gl.TEXTURE_2D, null);
  }

  add(gl, ...child) {
    const { children } = this;
    for (let obj of child) {
      children.push(obj);
      obj.init(gl);
    }
  }

  addTexure(gl, key, data) {
    this.mat.addMap(gl, key, data);
  }

  addTexureNew(gl, data) {
    this.mat.addMapNew(gl, data);
  }

  setProgram(program) {
    this.mat.program = program;
  }

  _drawArrays(gl, count, mode) {
    gl.drawArrays(gl[mode], 0, count);
  }

  _drawElements(gl, count, mode) {
    gl.drawElements(gl[mode], count, gl.UNSIGNED_SHORT, 0);
    // gl.drawElements(gl[mode], count, gl.UNSIGNED_BYTE, 0);//BYTE只能用于Uint8Array的elements绘制
  }

  allTransform() {
    const { mat, geo } = this;
    const { array, size } = geo.data["a_Position"];
    const { value } = mat.data["u_ModelM4"];

    let mm4 = new Matrix4();
    mm4.elements = value;

    let newArray = [];
    for (let i = 0; i < array.length; i += size) {
      let cur = null;
      if (size == 2) {
        cur = new Vector3(array[i], array[i + 1], 0);
      } else {
        cur = new Vector3(array[i], array[i + 1], array[i + 2]);
      }
      cur = cur.clone().applyMatrix4(mm4);
      if (size == 2) {
        newArray.push(cur.x, cur.y);
      } else {
        newArray.push(cur.x, cur.y, cur.z);
      }
    }
    // console.log(array)
    // console.log(newArray)
    //清空模型矩阵
    mat.setData("u_ModelM4", { value: new Matrix4().elements });
    // mat.data['u_ModelM4'].value = new Matrix4().elements;
    // mat.data['u_ModelM4'].needUpdate = false;
    //覆盖posiArray
    geo.setData("a_Position", { array: new Float32Array(newArray) });
  }

  clone() {
    return new Obj3D({
      mat: this.mat.clone(),
      geo: this.geo.clone(),
    });
  }

  default(data) {
    const curData = {};
    Object.assign(curData, obj3DAttr(), data);
    this.geo = new Geo({
      data: {
        a_Position: {
          size: curData.size,
          array: new Float32Array(curData.array),
        },
      },
    });
    if (curData.element) {
      this.geo.setElement(new Uint8Array(curData.element));
    }
    this.mat = new Mat({
      program: curData.program,
      mode: curData.mode,
      data: {
        u_PvM4: { type: "uniformMatrix4fv", value: curData.pvM4.elements },
        u_ModelM4: {
          type: "uniformMatrix4fv",
          value: curData.modelM4.elements,
        },
        u_PointSize: { type: "uniform1f", value: curData.PointSize },
        u_Color: { type: "uniform4fv", value: curData.color, cRend: true },
      },
    });
    return this;
  }

  createAxis(program, color = [1, 1, 1, 1], o = 0) {
    let create = function (array, color, element) {
      return new Obj3D({
        geo: new Geo({
          data: {
            a_Position: {
              size: 3,
              array: new Float32Array(array),
            },
          },
          element: {
            array: new Uint16Array(element),
          },
        }),
        mat: new Mat({
          program: program,
          mode: ["LINES"],
          data: {
            u_PvM4: { type: "uniformMatrix4fv", value: new Matrix4().elements },
            u_ModelM4: {
              type: "uniformMatrix4fv",
              value: new Matrix4().elements,
            },
            u_Color: { type: "uniform4fv", value: color },
          },
        }),
      });
    };
    let axis = create(
      [
        10 + o,
        0 + o,
        0 + o,
        -10 + o,
        0 + o,
        0 + o,
        0 + o,
        10 + o,
        0 + o,
        0 + o,
        -10 + o,
        0 + o,
        0 + o,
        0 + o,
        10 + o,
        0 + o,
        0 + o,
        -10 + o,
      ],
      color,
      [0, 1, 2, 3, 4, 5]
    );
    return axis;
  }
}

const obj3DAttr = () => ({
  program: null,
  array: [],
  element: null,
  pvM4: new Matrix4(),
  modelM4: new Matrix4(),
  color: [1, 1, 1, 1],
  PointSize: 30,
  size: 3,
  mode: ["POINTS"],
});
